//
//  regular_tetrahedron.cpp
//  OpenLearn
//
//  Created by korion chen on 2020/3/27.
//  Copyright © 2020 korion. All rights reserved.
//

#include "header/cube.hpp"
#include <iostream>
#include "model.h"

const float Cube::vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

const int Cube::indices[] = {
    0, 1, 3,
    1, 2, 3
};

const glm::vec3 Cube::cubePositions[] = {
  glm::vec3( 0.0f,  0.0f,  0.0f),
  glm::vec3( 2.0f,  5.0f, -15.0f),
  glm::vec3(-1.5f, -2.2f, -2.5f),
  glm::vec3(-3.8f, -2.0f, -12.3f),
  glm::vec3( 2.4f, -0.4f, -3.5f),
  glm::vec3(-1.7f,  3.0f, -7.5f),
  glm::vec3( 1.3f, -2.0f, -2.5f),
  glm::vec3( 1.5f,  2.0f, -2.5f),
  glm::vec3( 1.5f,  0.2f, -1.5f),
  glm::vec3(-1.3f,  1.0f, -1.5f)
};

const char* Cube::vertex_shader_code = ""
    "#version 330 core\n"
    "in vec3 Pos;\n"
    "in vec2 texPod;\n"
    "\n"
    "out vec2 TexCoord;\n"
    "\n"
    "uniform mat4 model;\n"
    "uniform mat4 view;\n"
    "uniform mat4 projection;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    gl_Position = projection * view * model * vec4(Pos, 1.0);\n"
    "    TexCoord = vec2(texPod.x, texPod.y);\n"
    "}\n";

const char* Cube::fragment_shader_code = ""
    "#version 330 core\n"
    "out vec4 FragColor;\n"
    "\n"
    "in vec2 TexCoord;\n"
    "\n"
    "uniform sampler2D texture0;\n"
    "uniform sampler2D texture1;\n"
    "\n"
    "void main()\n"
    "{\n"
    "    FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), 0.2);\n"
    "}\n";

int Cube::createShader(int type, const char *code){
    int shader = glCreateShader(type);
    glShaderSource(shader, 1, &code, NULL);
    glCompileShader(shader);
    int success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        char info[512];
        glGetShaderInfoLog(shader, 512, NULL, info);
        std::cout << "ERROR::SHADER::COMPILATION_FAILED\n"<< info << std::endl;
        return 0;
    }
    return shader;
}

int Cube::createProgram(){
    int vertexShader = createShader(GL_VERTEX_SHADER, vertex_shader_code);
    int fragmentShader = createShader(GL_FRAGMENT_SHADER, fragment_shader_code);
    if (!vertexShader || !fragmentShader) {
        return 0;
    }
    uint32_t program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    int success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        char info[512];
        glGetProgramInfoLog(program, 512, NULL, info);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"<< info << std::endl;
        return 0;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    return program;
}

int Cube::loadTexture(const char *path, bool reverseY){
    ImageData *data = new ImageData(path);
    bool result = data->loadData(reverseY);
    if (!result) {
        delete(data);
        std::cout << "LOAD IMAGE FAIL" << std::endl;
        return 0;
    }
    uint32_t texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    if (data->nrChannels == 3){
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, data->width, data->height, 0, GL_RGB, GL_UNSIGNED_BYTE, data->data);
    } else {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, data->width, data->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data->data);
    }
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, NULL);
    delete(data);
    return texture;
}

bool Cube::init(int width, int height){
    this->width = width;
    this->height = height;
    
    mProgram = createProgram();
    if (!mProgram) {
        return false;
    }
    glGenVertexArrays(1, &context);
    glBindVertexArray(context);
    
    glGenBuffers(1, &verticesBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, verticesBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    int pos = glGetAttribLocation(mProgram, "Pos");
    glVertexAttribPointer(pos, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(pos);
      
    int texPod = glGetAttribLocation(mProgram, "texPod");
    glVertexAttribPointer(texPod, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(texPod);
    
    glGenBuffers(1, &indicesBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
        
    glBindVertexArray(NULL);
    glBindBuffer(GL_ARRAY_BUFFER, NULL);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL);
    
    texture0 = loadTexture("/Users/korionchen/WorkSpace/person/OpenGLlearn/OpenLearn/OpenLearn/res/container.jpg", false);
       
    texture1 = loadTexture("/Users/korionchen/WorkSpace/person/OpenGLlearn/OpenLearn/OpenLearn/res/awesomeface.png", true);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    
    cameraPos = glm::vec3(0.0f, 0.0f,  3.0f);
    cameraUp = glm::vec3(0.0f, 1.0f,  0.0f);
    
    hasInit = true;
    return true;
}

float fov_range = 1;
float last_x = 0;
float last_y = 0;
float last_z = 0;

void Cube::drawSelf(GLFWwindow *window, float y, float x, float z, float yaw, float pitch){
    if (!hasInit) {
        return;
    }
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(mProgram);
    glUniform1i(glGetUniformLocation(mProgram, "texture0"), 0);
    glUniform1i(glGetUniformLocation(mProgram, "texture1"), 1);
    
//    model = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f * var1), glm::vec3(1.0f, 0.0f, 0.0f));
//    model = glm::rotate(model, glm::radians(90.0f * var2), glm::vec3(0.0f, 0.1f, 0.0f));

//    model = glm::rotate(glm::mat4(1.0f), (float)glm::radians(45.0f), glm::vec3(0.5f, 1.0f, 0.0f));
    
//    float distance = -10.0f * var1;
//    std::cout << "distance:" << distance << std::endl;

//    float x = -6.0f * var2, y = -6.0f * var1, z = -6.0f * var3;
//    std::cout << "x = " << x << ", y = " << y << ", z = " << z << std::endl;
//    view = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z));
    
    glm::vec3 front;
    
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(front);
    std::cout << "x:" << front.x << " y:" << front.y << " z:" << front.z << std::endl;
    
    float cameraSpeed = 0.05f;
    if (x > last_x)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (x < last_x)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    
    if (y > last_y)
        cameraPos += glm::normalize(cameraUp) * cameraSpeed;
    if (y < last_y)
        cameraPos -= glm::normalize(cameraUp) * cameraSpeed;

    if (z > last_z)
//        cameraPos += cameraSpeed * cameraFront;
        if (fov_range < 1.0f){
            fov_range += 0.05f;
        }
    if (z < last_z)
//        cameraPos -= cameraSpeed * cameraFront;
        if (fov_range > 0.0f){
            fov_range -= 0.05f;
        }
    
    last_x = x;
    last_y = y;
    last_z = z;
    
    
    view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
    
//    float fov = 90.0f * var2;
//    std::cout << "fov:" << fov << std::endl;
    projection = glm::perspective(glm::radians(80.0f * fov_range), (float)width/(float)height, 0.1f, 100.0f);
//    glUniformMatrix4fv(glGetUniformLocation(mProgram, "model"), 1, GL_FALSE, glm::value_ptr(model));
    glUniformMatrix4fv(glGetUniformLocation(mProgram, "view"), 1, GL_FALSE, glm::value_ptr(view));
    glUniformMatrix4fv(glGetUniformLocation(mProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
    
    glBindVertexArray(context);
//    glDrawArrays(GL_TRIANGLES, 0, 36);

    for(unsigned int i = 0; i < 10; i++){
        model = glm::mat4(1.0f);
        model = glm::translate(model, cubePositions[i]);
        float angle = 20.0f * (i+1);
        model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
        glUniformMatrix4fv(glGetUniformLocation(mProgram, "model"), 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
    glfwSwapBuffers(window);
}

void Cube::destroy(){
    glDeleteVertexArrays(1, &context);
    glDeleteBuffers(1, &verticesBuffer);
    glDeleteBuffers(1, &indicesBuffer);
}
